import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.atomic.AtomicBoolean;

// Nikolas Wolfe
// 2/26/09
// Chat Server

public class ChatServer
{
	// port, socket
	private final int port;
	private Thread writer;
	private Thread reader;
	private final AtomicBoolean terminate = new AtomicBoolean(false);
	private ServerSocket serverSocket;

	// establish input stream from keyboard
	private final InputStreamReader input = new InputStreamReader(System.in);
	private final BufferedReader userInput = new BufferedReader(input);
	
	public ChatServer(int portNumber) throws IOException
	{
		this.port = portNumber;
		try
		{
			// Start the server socket
			System.out.print("Starting server... ");
			this.serverSocket = new ServerSocket(port);
			System.out.println("Done!");
		}
		catch( IOException e )
		{
			System.out.println("Initialization failed!\n");
			terminate.set(true); 
			throw new IOException();
		}
	}
	
	public void setup() 
	{
		try
		{
			// set up connection socket
			System.out.print("\nWaiting for client... ");
			Socket connectionSocket = serverSocket.accept();
			System.out.println("Client detected!");
			
			// get user's name
			System.out.print("\nEnter your name: ");
			String name = userInput.readLine().toUpperCase();

			this.writer = new Thread(new Writer(connectionSocket, userInput, name));
			this.reader = new Thread(new Reader(connectionSocket));

			writer.start(); // kick off the typing functionality
			reader.start(); // kick off the receiving functionality
		} 
		catch ( IOException e )
		{
			System.out.println("Chat initialization failed!\n");
			terminate.set(true); 
		}
	}
	
	public void tearDown()
	{
		System.out.print("Shutting down chat service... ");
		try
		{
			writer.interrupt();
			reader.interrupt();
			writer.join();
			reader.join();
		} catch (InterruptedException e) {}
		System.out.println("Done!");
	}

	private class Writer implements Runnable
	{
		private final DataOutputStream outStream;
		private final BufferedReader input;
		private final String name;
		private String data;
		private static final String exit = "exit";
		
		public Writer(Socket s, BufferedReader br, String name) throws IOException
		{
			this.outStream = new DataOutputStream(s.getOutputStream());
			this.input = br;
			this.name = name;
		}
		
		public void run()
		{
			while( !Thread.interrupted() && !terminate.get() )
			{
				try
				{
					System.out.print(toString() + ": ");
					data = input.readLine();
					if( data.trim().equals(exit) )
					{
						tearDown(); // kill everyone!
					}
					outStream.writeBytes( toString() + ": " + data + '\n' );
				}
				catch( Exception e ) // InterruptedException or IOException 
				{
					Thread.currentThread().interrupt();
					terminate.set(true);
				}
			}
			System.out.println("Writer terminated!");
		}
		
		public String toString()
		{
			return this.name;
		}
	}
	
	private class Reader implements Runnable
	{
		private final BufferedReader inStream;
		
		public Reader(Socket s) throws IOException
		{
			this.inStream = new BufferedReader(
					new InputStreamReader(s.getInputStream()));
		}
		
		public void run()
		{
			while( !Thread.interrupted() && !terminate.get() )
			{
				try
				{
					// read input from client
					System.out.println(inStream.readLine());
				} 
				catch ( Exception e ) // InterruptedException or IOException 
				{
					Thread.currentThread().interrupt();
				}
			}
			System.out.println("Reader terminated!");
		}
	}
}
